﻿namespace EyeOpen.Windows.Form.Serialize
{
	using System;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Linq;
	using System.Linq.Expressions;
	using System.Reflection;
	using System.Windows.Forms;

	public abstract class BaseControlSerializer<T> : 
		IObjectSerializer where T : Control
	{
		private readonly EyeOpen.IContainer container;
		private readonly Type controlType;

		private HashSet<PropertyInfo> propertyDictionary;

		protected BaseControlSerializer(EyeOpen.IContainer container)
		{
			this.container = container;
			controlType = typeof(T);
		}

		public ControlSerialized Save(object control)
		{
			InitializeMap();
			
			var controlPropertyDictionary = new HashSet<Property>();

			foreach (var property in propertyDictionary)
			{
				SaveProperty(control, controlPropertyDictionary, property);
			}

			return
				new ControlSerialized
					{
						ControlId = ((Control)control).GetHierarchicalId(),
						PropertyCollection = controlPropertyDictionary
					};
		}

		public void Load(object control, ControlSerialized objectSerialized)
		{
			InitializeMap();

			foreach (var property in propertyDictionary)
			{
				LoadProperty(control, property, objectSerialized);
			}
		}

		protected abstract void Map();

		protected void Map<TProperty>(Expression<Func<T, TProperty>> property)
		{
			var propertyName = GetNameProperty(property);
			var propertyInfo = controlType.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);

			propertyDictionary.Add(propertyInfo);
		}

		private void InitializeMap()
		{
			if (propertyDictionary != null)
			{
				return;
			}

			propertyDictionary = new HashSet<PropertyInfo>();
			Map();
		}

		/// <exception cref="NotSupportedException">Cannot find a type converter for the type "{0}". Add the appropriate TypeConverter to the IOC container.</exception>
		private TypeConverter GetTypeConverter(PropertyInfo propertyInfo)
		{
			var typeConverter = 
				container
					.Resolve<TypeConverter>(propertyInfo.PropertyType);

			if (typeConverter == null)
			{
				var message =
					string.Format(
						"Cannot find a type converter for the type \"{0}\". Add the appropriate TypeConverter to the IOC container.",
						propertyInfo.PropertyType.FullName);

				throw 
					new NotSupportedException(message);
			}

			return typeConverter;
		}

		private void SaveProperty(object control, ISet<Property> controlPropertyDictionary, PropertyInfo propertyInfo)
		{
			var typeConverter = 
				GetTypeConverter(propertyInfo);

			var propertyToSerialize =
				new Property
				{
					Name = propertyInfo.Name,
					Type = propertyInfo.PropertyType.FullName,
					Value = typeConverter.ConvertToString(propertyInfo.GetValue(control, null))
				};

			controlPropertyDictionary.Add(propertyToSerialize);
		}

		private void LoadProperty(object control, PropertyInfo propertyInfo, ControlSerialized objectSerialized)
		{
			var property = 
				objectSerialized
					.PropertyCollection
					.Single(p => p.Name == propertyInfo.Name);

			var typeConverter = 
				GetTypeConverter(propertyInfo);

			propertyInfo.SetValue(control, typeConverter.ConvertFromString(property.Value), null);
		}

		/// <exception cref="ArgumentException">The given expression is not a property.</exception>
		private string GetNameProperty<TProperty>(Expression<Func<T, TProperty>> expression)
		{
			var memberExpression = expression.Body as MemberExpression;

			var memberPropertyExpression = memberExpression.Member as PropertyInfo;

			if (memberPropertyExpression == null)
			{
				throw new ArgumentException("The given expression is not a property.", "expression");
			}

			return memberPropertyExpression.Name;
		}
	}
}